/*
 * Copyright (c) 2009, 2012 IBM Corp.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Dave Locke - initial API and implementation and/or initial documentation
 */
package org.eclipse.paho.client.mqttv3.internal.security;

public class SimpleBase64Encoder {

    // if this string is changed, then the decode method must also be adapted.
    private final static String PWDCHARS_STRING = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private final static char[] PWDCHARS_ARRAY = PWDCHARS_STRING.toCharArray();

    /**
     * Encodes an array of byte into a string of printable ASCII characters using a base-64 encoding.
     *
     * @param bytes The array of bytes to e encoded
     * @return The encoded array.
     */
    public static String encode(byte[] bytes) {
        // Allocate a string buffer.
        int len = bytes.length;
        final StringBuffer encoded = new StringBuffer((len + 2) / 3 * 4);
        int i = 0;
        int j = len;
        while (j >= 3) {
            encoded.append(to64((((bytes[i] & 0xff) << 16)
                    | (int) ((bytes[i + 1] & 0xff) << 8) | (int) (bytes[i + 2] & 0xff)), 4));
            i += 3;
            j -= 3;
        }
        // j==2 | j==1 | j==0
        if (j == 2) {
            // there is a rest of 2 bytes. This encodes into 3 chars.
            encoded.append(to64(((bytes[i] & 0xff) << 8) | ((bytes[i + 1] & 0xff)), 3));
        }
        if (j == 1) {
            // there is a rest of 1 byte. This encodes into 1 char.
            encoded.append(to64(((bytes[i] & 0xff)), 2));
        }
        return encoded.toString();
    }

    public static byte[] decode(String string) {
        byte[] encoded = string.getBytes();
        int len = encoded.length;
        byte[] decoded = new byte[len * 3 / 4];
        int i = 0;
        int j = len;
        int k = 0;
        while (j >= 4) {
            long d = from64(encoded, i, 4);
            j -= 4;
            i += 4;
            for (int l = 2; l >= 0; l--) {
                decoded[k + l] = (byte) (d & 0xff);
                d = d >> 8;
            }
            k += 3;
        }
        // j==3 | j==2
        if (j == 3) {
            long d = from64(encoded, i, 3);
            for (int l = 1; l >= 0; l--) {
                decoded[k + l] = (byte) (d & 0xff);
                d = d >> 8;
            }
        }
        if (j == 2) {
            long d = from64(encoded, i, 2);
            decoded[k] = (byte) (d & 0xff);
        }
        return decoded;
    }

    /* the core conding routine. Translates an input integer into
     * a string of the given length.*/
    private final static String to64(long input, int size) {
        final StringBuffer result = new StringBuffer(size);
        while (size > 0) {
            size--;
            result.append(PWDCHARS_ARRAY[((int) (input & 0x3f))]);
            input = input >> 6;
        }
        return result.toString();
    }

    /*
     * The reverse operation of to64
     */
    private final static long from64(byte[] encoded, int idx, int size) {
        long res = 0;
        int f = 0;
        while (size > 0) {
            size--;
            long r = 0;
            // convert encoded[idx] back into a 6-bit value.
            byte d = encoded[idx++];
            if (d == '/') {
                r = 1;
            }
            if (d >= '0' && d <= '9') {
                r = 2 + d - '0';
            }
            if (d >= 'A' && d <= 'Z') {
                r = 12 + d - 'A';
            }
            if (d >= 'a' && d <= 'z') {
                r = 38 + d - 'a';
            }
            res = res + ((long) r << f);
            f += 6;
        }
        return res;
    }

}
